package com.piece.core.oauth.config;

import com.google.common.collect.Multimap;
import com.piece.core.authentication.handler.*;
import com.piece.core.authentication.provider.AuthenticationMobileProvider;
import com.piece.core.authentication.provider.AuthenticationOpenIdProvider;
import com.piece.core.authentication.provider.AuthenticationUsernameProvider;
import com.piece.core.web.authority.token.CompositeAuthorizationGranter;
import com.piece.core.framework.constant.CookieConstants;
import com.piece.core.oauth.filter.AuthenticationFilter;
import com.piece.core.oauth.filter.TokenFilter;
import com.piece.core.oauth.filter.ValidateCodeFilter;
import com.piece.core.framework.util.cache.CacheFactory;
import com.piece.core.framework.properties.ApplicationProperties;
import com.piece.core.verifycode.processor.ValidateCodeProcessorHolder;
import com.piece.core.web.authority.AccessDecisionManagerCustomizer;
import com.piece.core.web.authority.AuthorityFactory;
import com.piece.core.web.cookie.CookieSerializer;
import com.piece.core.web.properties.SecurityProperties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Lazy;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.preauth.AbstractPreAuthenticatedProcessingFilter;
import org.springframework.web.cors.CorsUtils;
import javax.annotation.Resource;
import java.util.List;

public class CustomWebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private ApplicationContext applicationContext;

    @Autowired
    private ApplicationProperties applicationProperties;

    @Autowired
    private SecurityProperties securityProperties;

    @Autowired
    private ValidateCodeProcessorHolder validateCodeProcessorHolder;

    @Autowired(required = false)
    private List<CompositeAuthorizationGranter> compositeAuthorizations;

    @Autowired
    private AuthenticationSuccessHandler authenticationSuccessHandler;

    @Autowired
    private AuthenticationFailureHandler authenticationFailureHandler;

    @Autowired
    private LogoutSuccessHandler logoutSuccessHandler;

    @Lazy
    @Resource
    private UserDetailsService userDetailsService;

    @Autowired
    private CacheFactory cacheFactory;

    @Autowired
    private AuthorityFactory authorityFactory;

    @Autowired
    private TokenStore tokenStore;

    @Autowired(required = false)
    private List<AuthorizeRequestsCustomizer> authorizeRequestsCustomizers;

    @Autowired(required = false)
    private AccessDecisionManagerCustomizer accessDecisionManagerCustomizer;

    private AuthenticationManager authenticationManager;

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        if (null == authenticationManager) {
            authenticationManager = super.authenticationManagerBean();
        }

        return authenticationManager;
    }

    @Bean(name = "cookieSerializer")
    @ConditionalOnMissingBean(CookieSerializer.class)
    public CookieSerializer cookieSerializer() {
        CookieSerializer tokenSerializer = new CookieSerializer();
        // sessionId名称
        tokenSerializer.setCookieName(CookieConstants.SESSION_KEY);
        tokenSerializer.setUseBase64Encoding(false);
        return tokenSerializer;
    }

    private ValidateCodeFilter validateCodeFilter() {
        ValidateCodeFilter validateCodeFilter = new ValidateCodeFilter(applicationProperties, securityProperties);
        validateCodeFilter.setValidateCodeProcessorHolder(validateCodeProcessorHolder);
        validateCodeFilter.setAuthenticationFailureHandler(authenticationFailureHandler);
        return validateCodeFilter;
    }

    private AuthenticationFilter authenticationFilter() {
        AuthenticationFilter authenticationFilter = new AuthenticationFilter(securityProperties.getProcess().getLoginProcessUrl(), compositeAuthorizations);
        authenticationFilter.setAuthenticationManager(authenticationManager);
        authenticationFilter.setAuthenticationSuccessHandler(authenticationSuccessHandler);
        authenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler);

        return authenticationFilter;
    }

    private TokenFilter tokenFilter() {
        return new TokenFilter(applicationContext, securityProperties, authorityFactory, tokenStore);
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        // 密码相关的配置
        http.formLogin()
                .loginPage(securityProperties.getProcess().getLoginUrl())
                .loginProcessingUrl(securityProperties.getProcess().getLoginProcessUrl())
                .successHandler(authenticationSuccessHandler)
                .failureHandler(authenticationFailureHandler);

        // 解决不允许显示在iframe的问题
        http.headers().frameOptions().disable();
        // 禁用缓存
        http.headers().cacheControl();
        // 登陆过滤器
        http.addFilterBefore(validateCodeFilter(), AbstractPreAuthenticatedProcessingFilter.class)
                .addFilterBefore(tokenFilter(), UsernamePasswordAuthenticationFilter.class)
                .addFilterAt(authenticationFilter(), UsernamePasswordAuthenticationFilter.class);

        Multimap<HttpMethod, String> permits = authorityFactory.permitAllMethods(applicationContext, securityProperties);
        http.authorizeRequests()
                // 无需安全认证地址
                .antMatchers(HttpMethod.GET, permits.get(HttpMethod.GET).toArray(new String[0]))
                .permitAll()
                .antMatchers(HttpMethod.POST, permits.get(HttpMethod.POST).toArray(new String[0]))
                .permitAll()
                .antMatchers(HttpMethod.PUT, permits.get(HttpMethod.PUT).toArray(new String[0]))
                .permitAll()
                .antMatchers(HttpMethod.DELETE, permits.get(HttpMethod.DELETE).toArray(new String[0]))
                .permitAll()
                .requestMatchers(EndpointRequest.toAnyEndpoint())
                .permitAll()
                .requestMatchers(CorsUtils::isPreFlightRequest)
                .permitAll();

        // 每个项目自定义安全配置
        if (null != authorizeRequestsCustomizers && authorizeRequestsCustomizers.size() > 0) {
            http.authorizeRequests(registry -> authorizeRequestsCustomizers.forEach(customizer -> customizer.customize(registry)));
        }

        if (null != accessDecisionManagerCustomizer) {
            http.authorizeRequests()
                .withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
                    @Override
                    public <O extends FilterSecurityInterceptor> O postProcess(O object) {
                        object.setAccessDecisionManager(accessDecisionManagerCustomizer);
                        return object;
                    }
                });
        }

        http.authorizeRequests().anyRequest().authenticated();

        // 登出相关的配置
        http.logout()
                .logoutUrl(securityProperties.getProcess().getLogoutUrl())
                .logoutSuccessHandler(logoutSuccessHandler)
                .deleteCookies(CookieConstants.SESSION_KEY, "JSESSIONID")
                .invalidateHttpSession(true)
                .clearAuthentication(true);

        // Session管理
        http.sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED);

        // 禁用CSRF攻击
        http.cors().and().csrf().disable();

        // 异常处理
        http.exceptionHandling()
                .authenticationEntryPoint(new AuthenticationEntryPointHandler(securityProperties))
                .accessDeniedHandler(new AccessDeniedHandler());
    }

    @Override
    public void configure(AuthenticationManagerBuilder builder) throws Exception {
        AuthenticationUsernameProvider provider_1 = new AuthenticationUsernameProvider();
        provider_1.setSecurityProperties(securityProperties);
        provider_1.setUserDetailsService(userDetailsService);
        provider_1.setCacheFactory(cacheFactory);
        builder.authenticationProvider(provider_1);

        AuthenticationMobileProvider provider_2 = new AuthenticationMobileProvider();
        provider_2.setSecurityProperties(securityProperties);
        provider_2.setUserDetailsService(userDetailsService);
        provider_2.setCacheFactory(cacheFactory);
        builder.authenticationProvider(provider_2);

        AuthenticationOpenIdProvider provider_3 = new AuthenticationOpenIdProvider();
        provider_3.setUserDetailsService(userDetailsService);
        builder.authenticationProvider(provider_3);
    }

    @Override
    @Deprecated
    public void configure(WebSecurity web) throws Exception {
        // 静态资源文件
        Multimap<HttpMethod, String> permits = authorityFactory.permitAllSource();
        // 忽略静态资源
        web.ignoring().antMatchers(permits.get(HttpMethod.GET).toArray(new String[0]));
    }
}
